﻿using System.Collections.Generic;
using UnityEngine;
using Unity.Mathematics;
using Jinndev.Modding;

namespace Jinndev.Tile2D {

    public static class AtlasBatcher {

        public class Atlas {
            public Texture2D texture;
            public string name;
        }


        public static bool enableBatchAtlas = true;
        public static int maxAtlasSize = 1024;
        public static MultiMap<Mod, Atlas> atlasMap = new MultiMap<Mod, Atlas>();

        public static void BatchRenderConfigAtlas(IEnumerable<Config> configList) {
            if (!enableBatchAtlas) {
                return;
            }

            // 分批次
            MultiMap<Mod, RenderConfig> batchMap = new MultiMap<Mod, RenderConfig>();
            foreach (Config config in configList) {
                if (config.HasSubConfig<RenderConfig>()) {
                    batchMap.Add(config.mod, config.GetSubConfig<RenderConfig>());
                }
            }

            foreach(var pair in batchMap) {
                Batch1(pair.Key, pair.Value);
            }
            Debug.Log($"Atlas count: {atlasMap.Size()}");
        }

        class Pack {
            public List<Vector2> sizeList = new List<Vector2>();
            public List<Texture2D> textureList = new List<Texture2D>();
            public List<List<RenderConfig>> configList = new List<List<RenderConfig>>();
        }

        // 使用Unity提工的packing方法
        // 先按texture分组RenderConfig
        // 计算打包批次
        // 打包
        private static void Batch1(Mod mod, List<RenderConfig> configs) {
            if (configs == null || configs.Count == 0) {
                return;
            }

            MultiMap<Texture2D, RenderConfig> textureConfigMap = new MultiMap<Texture2D, RenderConfig>();
            foreach (RenderConfig config in configs) {
                Texture2D texture = config.GetTexture();
                if (texture == null || !texture.isReadable) {
                    //Debug.Log($"{config.texture} isn't readable");
                    continue;
                }
                textureConfigMap.Add(texture, config);
            }


            List<Pack> packList = new List<Pack>();
            Pack pack = new Pack();
            packList.Add(pack);


            foreach(var pair in textureConfigMap) {
                Texture2D texture = pair.Key;
                RenderConfig firstConfig = pair.Value[0];

                pack.sizeList.Add(new Vector2(texture.width, texture.height));
                // 先判断当前图片添加进去后，还能不能容得下
                if (!CommonUtil.FitAtlas(pack.sizeList.ToArray(), 10, maxAtlasSize, new List<Rect>())) {
                    // 容不下则新建一个打包批次
                    pack = new Pack();
                    packList.Add(pack);
                }
                pack.textureList.Add(texture);
                pack.configList.Add(pair.Value);
            }

            // 按批次打图集
            foreach (var p in packList) {
                Texture2D atlas = new Texture2D(maxAtlasSize, maxAtlasSize, TextureFormat.ARGB32, false);
                atlas.filterMode = FilterMode.Point;
                atlas.SetPixels(0, 0, atlas.width, atlas.height, new Color[atlas.width * atlas.height]);

                Rect[] rects = atlas.PackTextures(p.textureList.ToArray(), 0, maxAtlasSize);

                if(rects.Length != p.textureList.Count) {
                    Debug.LogError($"Pack result count '{rects.Length}' doesn't match textures count '{p.textureList.Count}'");
                    Object.DestroyImmediate(atlas);
                    continue;
                }

                for (int i = 0; i < rects.Length; i++) {
                    Rect rect = rects[i];
                    List<RenderConfig> configList = p.configList[i];

                    foreach(RenderConfig config in configList) {
                        config.SetTexture(atlas);
                        config.SetAtlasTilingOffset(new float4(rect.width, rect.height, rect.x, rect.y));
                    }
                }

                atlasMap.Add(mod, new Atlas { name = mod.name, texture = atlas });
            }

            foreach (RenderConfig config in configs) {
                config.InitRenderer();
            }
        }

        // 自己实现的packing方法
        private static void Batch2(Mod mod, List<RenderConfig> configs) {
            if (configs == null || configs.Count == 0) {
                return;
            }
            Texture2D atlas = new Texture2D(maxAtlasSize, maxAtlasSize, TextureFormat.ARGB32, false);
            atlas.filterMode = FilterMode.Point;
            atlas.SetPixels(0, 0, atlas.width, atlas.height, new Color[atlas.width * atlas.height]);

            Dictionary<string, RenderConfig> sameAtlas = new Dictionary<string, RenderConfig>();
            int x = 0;
            int y = 0;
            int maxY = 0;
            foreach (RenderConfig config in configs) {
                Texture2D src = config.GetTexture();
                if (!src.isReadable) {
                    //Debug.Log($"{config.texture} isn't readable");
                    continue;
                }
                if (sameAtlas.TryGetValue(config.texture, out RenderConfig sameAtlasConfig)) {
                    config.SetTexture(sameAtlasConfig.GetTexture());
                    config.SetAtlasTilingOffset(sameAtlasConfig.GetAtlasTilingOffset());
                    continue;
                }

                if (x + src.width > maxAtlasSize) {
                    x = 0;
                    y += maxY;
                }
                if (y + src.height > maxAtlasSize) {
                    atlas.Apply(true, true);
                    atlasMap.Add(mod, new Atlas { name = mod.name, texture = atlas });
                    x = 0;
                    y = 0;
                    maxY = 0;
                    atlas = new Texture2D(maxAtlasSize, maxAtlasSize);
                }

                atlas.SetPixels(x, y, src.width, src.height, src.GetPixels());

                config.SetTexture(atlas);
                config.SetAtlasTilingOffset(new float4((float)src.width / maxAtlasSize, (float)src.height / maxAtlasSize, (float)x / maxAtlasSize, (float)y / maxAtlasSize));
                sameAtlas.Add(config.texture, config);

                x += src.width;
                maxY = math.max(maxY, src.height);
            }
            if (x > 0 || y > 0) {
                atlas.Apply(true, true);
                atlasMap.Add(mod, new Atlas { name = mod.name, texture = atlas });
            }
            else {
                Object.Destroy(atlas);
            }

            foreach (RenderConfig config in configs) {
                config.InitRenderer();
            }

        }
    }

}